﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using System.IO;

namespace MarioArenaLevelEditor
{
    public class Map
    {
        public const int ViewWidth = 32;
        public const int ViewHeight = 24;
        public int GridWidth = 36;
        public int GridHeight = 24;

        public int SelectedTileType = 0;
        public bool showGrid = false;
        public Pen GridPen = new Pen(Color.Black);

        private int imageSize = 24;
        private float scale = 0.5f;
        private int marginX = 0;
        private int marginY = 0;
        private bool draw = false;
        private bool rightClick = false;
        private Point rightClickIndex;

        private int[,] mapGrid;
        private int[,] historyGrid;

        private List<Point> changedIndexes = new List<Point>();
        private List<int> previousChars = new List<int>();

        #region Singleton
        private static Map instance = new Map();
        private Map()
        {
            BuildGrid();
        }
        public static Map Instance
        {
            get
            {
                return instance;
            }
        }
        #endregion


        /// <summary>
        /// Draws the the entire map/grid
        /// </summary>
        /// <param name="e"></param>
        public void Draw(PaintEventArgs e)
        {
            for (int x = 0; x < ViewWidth; x++)
            {
                for (int y = 0; y < ViewHeight; y++)
                {
                    int indexX = x + marginX;
                    int indexY = y + marginY;
                    Bitmap textureToDraw = TextureManager.GetMatchingTexture(mapGrid[indexX, indexY]); //Get texture by character
                    //Bitmap will be null if . is detected (empty tile)
                    int drawPosX = x * imageSize;
                    int drawPosY = y * imageSize;
                    if (textureToDraw != null)
                        e.Graphics.DrawImage(textureToDraw, new Rectangle(drawPosX, drawPosY, imageSize, imageSize));
                    if (showGrid)
                    {
                        e.Graphics.DrawLine(GridPen, drawPosX, drawPosY, drawPosX + imageSize, drawPosY); //X
                        e.Graphics.DrawLine(GridPen, drawPosX, drawPosY, drawPosX, drawPosY + imageSize); //X
                    }
                }
            }
        }

        /// <summary>
        /// Called when mouse is clicked down.
        /// </summary>
        /// <param name="leftMouseButton">True if the button which is down is the left mouse button</param>
        public void MouseDown(bool leftMouseButton)
        {
            if (leftMouseButton)
            {
                draw = true;
            }
            else
            {
                Point point = getMouseIndex();
                if (point.X != -1 && point.Y != -1)
                {
                    rightClick = true;
                    this.rightClickIndex = point;
                }
            }
        }
        /// <summary>
        /// Called when the mouse was down before and is now up again (released).
        /// </summary>
        public void MouseUp()
        {
            //If right mouse click on ONE single index
            if (draw == false && this.rightClick && getMouseIndex().Equals(rightClickIndex))
                UndoIndex(rightClickIndex);
            else
            {
                //Always change to the selected tile, also on click (when mouse doesnt move)
                MouseMove();
                draw = false;
            }
        }
        /// <summary>
        /// Called when the mouse is moving. When mouse position is within the panel, a texture will be drawn at that position.
        /// </summary>
        /// <returns></returns>
        public bool MouseMove()
        {
            if (draw)
            {
                Point mouseIndex = getMouseIndex();
                int x = mouseIndex.X;
                int y = mouseIndex.Y;

                //NEVER ALLOW CHANGE OF THE BOTTOM LAYER (GROUND) so y -1
                if (y < this.mapGrid.GetLength(1) -1 && y >= 0)
                {
                    //Store the old character so it can be undone with right mouse button
                    if (!this.mapGrid[x, y].Equals(this.SelectedTileType))
                    {
                        SaveChanges(x, y);
                        this.mapGrid[x, y] = this.SelectedTileType;
                    }
                    return true;
                }
            }
            return false;
        }
        /// <summary>
        /// Saves changes made by storing the changed item in the grid.
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        private void SaveChanges(int x, int y)
        {
            this.historyGrid[x, y] = this.mapGrid[x, y];
            Console.WriteLine("Storing old: " + this.mapGrid[x, y]);
        }

        /// <summary>
        /// Retrieves the position of the mouse as an index in the map grid.
        /// </summary>
        /// <returns></returns>
        private Point getMouseIndex()
        {
            Point position = UI.MapPanel.PointToScreen(Point.Empty);
            int relativeX = Cursor.Position.X - position.X;
            int relativeY = Cursor.Position.Y - position.Y;
            int x = 0;
            int y = 0;

            //Calculate the index, and correct if out of bounds of the panel
            if (relativeX > 0)
            {
                if (relativeX < UI.MapPanel.Width)
                    x = relativeX / imageSize; //Get the index of the square the mouse is in.
                else x = ViewWidth - 1;
            }
            else x = 0;
            if (relativeY > 0)
            {
                if (relativeY < UI.MapPanel.Height)
                    y = relativeY / imageSize;
                else y = ViewHeight - 1;
            }
            else y = 0;

            return new Point(x + marginX, y + marginY); //Add marginX and marginY to compensate the scrolling.
        }

        /// <summary>
        /// Undo action on a specific index. Will restore the previously stored texture at the desired index.
        /// </summary>
        /// <param name="index">Index in the grid to restore</param>
        private void UndoIndex(Point index)
        {
            int currentchar = mapGrid[index.X, index.Y];
            int historychar = historyGrid[index.X, index.Y];
            mapGrid[index.X, index.Y] = historychar;
            historyGrid[index.X, index.Y] = currentchar;
            Console.WriteLine("Restored to " + historychar + " from " + currentchar);
        }

        /// <summary>
        /// Shift one tile to the right.
        /// </summary>
        public void MoveRight()
        {
            //If the end will not be reached
            if (this.mapGrid.GetLength(0) >= (marginX + 1 + ViewWidth))
            {
                this.marginX++; //shift one tile to the right
                //After the shift to the right
                if (marginX == (mapGrid.GetLength(0) - ViewWidth)) //if the end is reached
                    UI.ButtonRight.Enabled = false;
                UI.ButtonLeft.Enabled = true;
            }
        }

        /// <summary>
        /// Shift one tile to the left.
        /// </summary>
        public void MoveLeft()
        {
            if (marginX > 0)
            {
                this.marginX--; //shift one tile to the left
                //After the shift to the left
                if (marginX == 0) //if the minimum is reached
                    UI.ButtonLeft.Enabled = false;
                UI.ButtonRight.Enabled = true;
            }
        }

        /// <summary>
        /// Shift one tile up
        /// </summary>
        public void MoveUp()
        {
            if (marginY > 0) //only allow scroll when not at the topmost end.
            {
                this.marginY--;
                if (marginY == 0) //reached top after this change
                    UI.ButtonUp.Enabled = false;
                UI.ButtonDown.Enabled = true;
            }
            Console.WriteLine("MarginY is now: " + marginY);
        }

        /// <summary>
        /// Shift one tile down
        /// </summary>
        public void MoveDown()
        {
            //If the end will not be reached when going down a step
            if (this.mapGrid.GetLength(1) >= (marginY + 1 + ViewHeight))
            {
                this.marginY++; //go one tile down
                if (marginY == (mapGrid.GetLength(1) - ViewHeight)) //if the end is reached
                    UI.ButtonDown.Enabled = false;
                UI.ButtonUp.Enabled = true;
            }
        }

        /// <summary>
        /// Changes the width of the grid to a new width.
        /// </summary>
        /// <param name="newWidth">Width to used.</param>
        public void ChangeSizeX(int newWidth)
        {
            int mapWidth = this.mapGrid.GetLength(0);
            int mapHeight = this.mapGrid.GetLength(1);
            if (newWidth < mapWidth)
                MessageBox.Show("Size reduction is not yet available in this version.");
            else if (newWidth > mapWidth)
            {
                //Add extra columns
                int extraColumns = newWidth - mapWidth;
                int[,] newMapGrid = new int[newWidth, mapHeight];
                int[,] newHistoryGrid = new int[newWidth, mapHeight];
                //Fill new grid with the old one.
                for (int y = 0; y < mapHeight-1; y++)
                {
                    for (int x = 0; x < newWidth; x++)
                    {
                        //If index exists in old grid
                        if (x < mapWidth)
                        {
                            newMapGrid[x, y] = this.mapGrid[x, y];
                            newHistoryGrid[x, y] = this.historyGrid[x, y];
                        }
                        else newMapGrid[x, y] = 0;
                    }
                }
                this.mapGrid = newMapGrid;
                this.GridWidth = newMapGrid.GetLength(0);
                this.historyGrid = newHistoryGrid;
                BuildGround();
                Console.WriteLine("Width changed to " + mapGrid.GetLength(0));
            }
        }

        /// <summary>
        /// Changes the height of the grid to a new height.
        /// </summary>
        /// <param name="newWidth">Width to used.</param>
        public void ChangeSizeY(int newHeight)
        {
            int mapWidth = this.mapGrid.GetLength(0);
            int mapHeight = this.mapGrid.GetLength(1);
            if (newHeight < mapHeight)
                MessageBox.Show("Size reduction is not yet available in this version.");
            else if (newHeight > mapHeight)
            {
                //Add extra rows TO THE TOP
                int extraRows = newHeight - mapHeight;
                //Recreate grids with the new height
                int[,] newMapGrid = new int[mapWidth, newHeight];
                int[,] newHistoryGrid = new int[mapWidth, newHeight];
                //calculate the new marginY, which is the difference between the old height and new height.
                this.marginY = newHeight - mapHeight;
                //Fill new grid with the old one.
                for (int y = 0; y < newHeight; y++)
                {
                    for (int x = 0; x < mapWidth; x++)
                    {
                        //If index exists in old grid
                        if (y < mapHeight)
                        {
                            //Place previous tiles on y + marginY because the map is automatically scrolled down
                            newMapGrid[x, y + this.marginY] = this.mapGrid[x, y];
                            newHistoryGrid[x, y + this.marginY] = this.historyGrid[x, y];
                        }
                    }
                }
                //automatically scroll all the way down (so add a margin at top)
                
                this.mapGrid = newMapGrid;
                this.GridHeight = newMapGrid.GetLength(1);
                this.historyGrid = newHistoryGrid;
                
                Console.WriteLine("Height changed to " + mapGrid.GetLength(1));
            }
        }

        /// <summary>
        /// Fills the grid with its initial values: all items are empty tiles and a floor is placed on the bottom.
        /// </summary>
        private void BuildGrid()
        {
            int tileNumber = 0;
            mapGrid = new int[GridWidth, GridHeight]; //initialize with default values.
            historyGrid = new int[GridWidth, GridHeight];
            for (int x = 0; x < GridWidth; x++)
            {
                for (int y = 0; y < GridHeight -1; y++)
                {
                    tileNumber = 0; //air
                    this.mapGrid[x, y] = tileNumber;
                    this.historyGrid[x, y] = tileNumber;
                }
            }
            BuildGround();
        }
        /// <summary>
        /// Builds the ground layer of the map, which is fixed and cannot be changed by the user.
        /// </summary>
        private void BuildGround()
        {
            for (int x = 0; x < GridWidth; x++)
            {
                int tileNumber = 11; //ground
                if (x == 0)
                    tileNumber = 10;
                if(x == GridWidth-1)
                    tileNumber = 12;

                this.mapGrid[x, GridHeight-1] = tileNumber;
                this.historyGrid[x, GridHeight-1] = tileNumber;
                
            }
        }

        /// <summary>
        /// Generates data for file so the created map can be saved.
        /// </summary>
        /// <returns></returns>
        public string GenerateFile()
        {
            int levelWidth = this.mapGrid.GetLength(0);
            int levelHeight = this.mapGrid.GetLength(1);
            string saveData = levelWidth + ":" + levelHeight + ":";
            int spawnPoints = 0;
            
            int previousTile = 0;
            int previousX = 0;
            int previousY = 0;

            for (int y = 0; y < levelHeight; y++)
            {
                for (int x = 0; x < levelWidth; x++)
                {
                    int foundTile = this.mapGrid[x, y];

                    if (foundTile == 100)
                        spawnPoints++;

                    if (foundTile != previousTile) //A different tile than the previous is found
                    {
                        if (previousTile != 0) //if the previous tile was not empty
                            saveData += previousX + "#" + previousY + "#" + previousTile + "|"; //Close the previous tile row first
                        
                        if (foundTile != 0) //if the found tile is not empty, so a block was found
                            saveData += x + "#" + y + "#"; //add new tile row start positions
                    }

                    previousX = x;
                    previousY = y;
                    previousTile = foundTile;
                }
            }
            if (previousTile != 0)//Close very last tile of the map)
                saveData += previousX + "#" + previousY + "#" + previousTile; //no pipeline

            if (spawnPoints > 0)
            {
                Console.Write(saveData);
                return saveData;
            }
            else return null;
        }

        
    }
}
